package com.pmdesigns.sqlmapper;

import java.util.*;


/**
 * This class is basically just a data structure for holding
 * information extracted from an annotated database table
 * definition.
 * <p>
 * It is combined with a template file to produce a model
 * source code file that maps to a database table.
 * <p>
 * It is intened that its public fields be accessed directly from java-code-tags
 * and java-expression-tags in template files.
 *
 * @author mike dooley
 */
public class Model implements Comparable<Model> {
	/**
	 * The database table name
	 */
	public String tableName;

	/**
	 * The model class name
	 */
	public String className;

	/**
	 * The model class package
	 */
	public String classPkg;

	/**
	 * The fields in this model.
	 */
	public Set<Field> fields = new TreeSet<Field>();

	/**
	 * The models that this model owns a single instance of.
	 */
	public List<ForeignKey> has1 = new ArrayList<ForeignKey>();

	/**
	 * The models that this model owns multiple instances of.
	 */
	public List<ForeignKey> hasMany = new ArrayList<ForeignKey>();
	
	/**
	 * The models that this model belongs to.
	 */
 	public List<ForeignKey> belongs2 = new ArrayList<ForeignKey>();

	/**
	 * The join tables that define many-to-many relationships with other models
	 */
	public List<JoinedModels> joined = new ArrayList<JoinedModels>();

	/**
	 * Flag indicating if this is a 'join table' model
	 */
	public boolean isJoin;

	/**
	 * The name of the template to be used to generate the source code for this model.
	 */
	public String template;
	
	/**
	 * The file suffix to use for the source code file that this model generates.
	 */
	public String fileSuffix;
	
	boolean isMapped;
	int beginLine;

	
	Model(Token t) {
		this.beginLine = t.beginLine;
	}

	public String getColumnNamesStr() {
		return getColumnNamesStr("");
	}
	
	public String getColumnNamesStr(String prefix) {
		StringBuilder sb = new StringBuilder();
		for (Field f : fields) {
			if (f.notMapped) continue;
			sb.append(prefix).append(f.columnName).append(",");
		}
		if (sb.length() > 1) {
			sb.setLength(sb.length()-1);
		}
		return sb.toString();
	}
	
	public int compareTo(Model m) {
		if (m == null) return 1;
		return tableName.compareTo(m.tableName);
	}
	
	void extractTableName(Token t) throws ParseException {
		String s = t.image;
		int n = s.length();
		if (n < 16) throw new ParseException("Invalid table definition '"+s+"' on line "+t.beginLine);
		tableName = s.substring(12, n-1).trim();
	}
	
	void addAttr(Token t) throws ParseException {
		String s = t.image.trim();
		if ("join".equals(s)) {
			isJoin = true;
		} else {
			throw new ParseException("Unknown model attr '"+s+"' on line "+t.beginLine);
		}
	}

	void addKeyValue(Token t) throws ParseException {
		String s = t.image.trim();
		int idx = s.indexOf('=');
		if (idx == -1) throw new ParseException("Invalid model key/value '"+s+"' on line "+t.beginLine);
		String key = s.substring(0,idx);
		String val = s.substring(idx+1);
		if ("name".equals(key)) {
			className = val;
		} else if ("pkg".equals(key)) {
			classPkg = val;
		} else if ("template".equals(key)) {
			template = val;
		} else if ("suffix".equals(key)) {
			fileSuffix = val;
		} else {
			throw new ParseException("Unknown model key/value '"+s+"' on line "+t.beginLine);
		}
	}

	void addField(Field f) {
		fields.add(f);
	}

	void addHas1(ForeignKey fk) {
		has1.add(fk);
	}

	void addHasMany(ForeignKey fk) {
		hasMany.add(fk);
	}

	void addBelongs2(ForeignKey fk) {
		belongs2.add(fk);
	}

	void addJoin(Model joinedBy, Field thisKey, Field thatKey, Model joinedTo) {
		joined.add(new JoinedModels(joinedBy, thisKey, thatKey, joinedTo));
	}

	/**
	 * For debugging
	 */
	public String toString() {
		StringBuilder sb = new StringBuilder();
		sb.append("table: ").append(tableName).append(", join: ").append(isJoin);
		sb.append(", mapped: ").append(isMapped);
		sb.append(", class: ").append(className).append(", pkg: ").append(classPkg);
		sb.append(", template: ").append(template).append(", fileSuffix: ").append(fileSuffix);
		sb.append(", beginLine: ").append(beginLine);
		for (Field f : fields) sb.append("\n").append(f);
		sb.append("\nhas1:");
		for (ForeignKey fk : has1) sb.append(" ").append(fk.model.className);
		sb.append("\nhasMany:");
		for (ForeignKey fk : hasMany) sb.append(" ").append(fk.model.className);
		sb.append("\nbelongs2:");
		for (ForeignKey fk : belongs2) sb.append(" ").append(fk.model.className);
		sb.append("\njoined:");
		for (JoinedModels jm : joined) {
			sb.append(" ").append(jm.joinedBy.className).append(".(").append(jm.thisKey.fieldName);
			sb.append(",").append(jm.thatKey.fieldName).append(")->").append(jm.joinedTo.className);
		}
		return sb.toString();
	}
}
